package com.sunday.common.statemachine.study.e9_Factory_Adapter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;

/**
 * <a href="https://docs.spring.io/spring-statemachine/docs/4.0.0/reference/index.html#factory-through-an-adapter">...</a>
 * 用StateMachineModelFactory
 * 在幕后，所有机器配置首先被转换为 so that need need know 从配置的来源，因为机器可以构建 Java 配置、UML 或存储库。
 * 如果你想疯狂，你也可以使用 自定义 ，这是尽可能最低的 定义配置的级别。StateMachineModelStateMachineFactoryStateMachineModel
 * <p>
 * 所有这些都与 a 有什么关系？ 还有一个具有以下签名的方法：
 * 实现可以选择使用该方法。machineIdStateMachineModelFactoryStateMachineModel<S, E> build(String machineId)StateMachineModelFactory
 * <p>
 * RepositoryStateMachineModelFactory（请参阅存储库支持）用于支持持久 store 通过 Spring Data Repository 接口。
 * 例如，both 和 都有一个方法 （），用于构建不同的状态和 过渡 .其中 ， 如果用作空 或 NULL，
 * 则默认为 repository configuration（在后备持久模型中） 没有已知的计算机 ID。
 * machineIdStateRepositoryTransitionRepositoryList<T> findByMachineId(String machineId)machineIdRepositoryStateMachineModelFactorymachineId
 */

@Slf4j
@Configuration  // 声明该类为Spring的配置类
@EnableStateMachineFactory
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {

    @Override
    public void configure(StateMachineConfigurationConfigurer<OrderStates, OrderEvents> config) throws Exception {
        config
                .withConfiguration()
                .autoStartup(true)
                .listener(listener());
    }

    @Override
    public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
        states
                .withStates()
                .initial(OrderStates.S1)
                .fork(OrderStates.S2)
                .state(OrderStates.S3)
                .join(OrderStates.S4)
                .state(OrderStates.S5)
                .and()

                .withStates()
                .parent(OrderStates.S3)
                .initial(OrderStates.S2I)
                .state(OrderStates.S21)
                .state(OrderStates.S22)
                .end(OrderStates.S2F)
                .and()

                .withStates()
                .parent(OrderStates.S3)
                .initial(OrderStates.S3I)
                .state(OrderStates.S31)
                .state(OrderStates.S32)
                .end(OrderStates.S3F)
        ;

    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
        transitions
                // 新建订单 -> 检查库存 触发条件 检查库存事件
                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S1)
                .target(OrderStates.S2)
                .event(OrderEvents.TS2)
                .action(context -> {
                    log.info("TS2 : {}", context.getStateMachine().getState().getId());
                })
                .and()

                .withFork()
                .source(OrderStates.S2)
                .target(OrderStates.S21)
                .target(OrderStates.S31)
                .and()

                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S21)
                .target(OrderStates.S22)
                .event(OrderEvents.TS22)
                .action(context -> {
                    log.info("TS22 triggered, transitioning from {} to {}", context.getSource().getId(), context.getTarget().getId());
                })
                .and()

                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S31)
                .target(OrderStates.S32)
                .event(OrderEvents.TS32)
                .action(context -> {
                    log.info("TS32 triggered, transitioning from {} to {}", context.getSource().getId(), context.getTarget().getId());
                })
                .and()

                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S22)
                .target(OrderStates.S2F)
                .event(OrderEvents.TS2F)
                .action(context -> {
                    log.info("TS2F triggered, transitioning from {} to {}", context.getSource().getId(), context.getTarget().getId());
                })
                .and()

                .withExternal()  // 配置外部状态转换（即不同状态之间的转换）
                .source(OrderStates.S32)
                .target(OrderStates.S3F)
                .event(OrderEvents.TS3F)
                .action(context -> {
                    log.info("TS3F triggered, transitioning from {} to {}", context.getSource().getId(), context.getTarget().getId());
                })
                .and()

                // 多个源状态汇聚到 S4（通过 join）
                .withJoin()
                .source(OrderStates.S2F)
                .source(OrderStates.S3F)
                .target(OrderStates.S4)
                .and()

                // 从 S4 到 S5 的普通状态转换，带 Guard 表达式
                .withExternal()
                .source(OrderStates.S4)
                .target(OrderStates.S5)
                .guard(context -> {
                    context.getExtendedState();
                    return true;
                })
//                .guardExpression("!extendedState.variables.isEmpty()")  // 只有 extendedState 中有数据时才转移
//                .guardExpression("true")
                .action(context -> {
                    log.info("Transitioning from {} to {} based on guard expression", context.getSource().getId(), context.getTarget().getId());
                })
                .and()

                // 当 extendedState 为空时，跳到终态
                .withExternal()
                .source(OrderStates.S4)
                .target(OrderStates.SF)
//                .guardExpression("extendedState.variables.isEmpty()") // extendedState 为空时转移到终态
                .action(context -> {
                    log.info("Transitioning from {} to {} based on guard expression", context.getSource().getId(), context.getTarget().getId());
                });
        ;

    }

    /**
     * 为什么触发两次 stateChanged()？
     * <p>
     * 因为状态机中存在层次结构，从 UNPAID 到 PAID 是一次状态变更，而从 PAID 自动切换到 PENDING_OUTBOUND 是另一层次变更。
     * 为什么 UNPAID to PAID 出现在 PENDING_OUTBOUND 后？
     * <p>
     * 日志记录的顺序是由状态进入的顺序决定的：先进入 PAID，再进入其子状态 PENDING_OUTBOUND。但 PENDING_OUTBOUND 是最终的细化状态，因此先记录。
     * 如何解决？
     * <p>
     * 在 stateChanged() 方法中通过 getParent() 方法提取父状态，记录完整的父子状态信息。
     * 也可以考虑使用 entry() 和 exit() 钩子方法进行状态进入和退出日志的更精确记录。
     */
    @Bean
    public StateMachineListener<OrderStates, OrderEvents> listener() {
        return new StateMachineListenerAdapter<>() {
            @Override
            public void stateChanged(State<OrderStates, OrderEvents> from, State<OrderStates, OrderEvents> to) {
                log.info("State change from {} to {}", from == null ? "无" : from.getId(), to.getId());
            }
        };
    }

}